First-pass attempt at characterizing calcium spikes
# install.packages("Rcatch22")
# install.packages("dichromat")
# install.packages("DescTools")
library(Rcatch22)
library(smooth)
Loading required package: greybox
Package "greybox", v2.0.0 loaded.
This is package "smooth", v4.0.0
library(tidyverse)
── Attaching core tidyverse packages ───────────────────────────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.4 ✔ readr 2.1.5
✔ forcats 1.0.0 ✔ stringr 1.5.1
✔ ggplot2 3.4.4 ✔ tibble 3.2.1
✔ lubridate 1.9.3 ✔ tidyr 1.3.0
✔ purrr 1.0.2 ── Conflicts ─────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ lubridate::hm() masks greybox::hm()
✖ dplyr::lag() masks stats::lag()
✖ tidyr::spread() masks greybox::spread()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
source("traceAnalysisFxns.r")
numRuns <- function(x, threshold=0.2,len=2)
{
x[x>=threshold]
out <- rle(x)$lengths
length(out[out>=len])
}
numSpikes <- function(x, threshold=0.17)
{
length(which(x>=threshold))
}
avgSpikeLength <- function(x, threshold=0.17)
{
pts_above_th <- x[x>=threshold]
out <- tryCatch({rle(pts_above_th)$lengths},error=function(cond){NA})
tryCatch({mean(out)},error=function(cond){message(cond);NA})
}
avgTimeBetweenSpikes <- function(x, threshold=0.17)
{
pts_above_th <- x[x>=threshold]
out <- tryCatch({inverse.rle(pts_above_th)$lengths},error=function(cond){NA})
tryCatch({mean(out)},error=function(cond){NA})
}
std_colnames <- c("reg_id","im_idx","time_sec","fura_ratio","expt_date")
fixColnames <- function(x) sapply(x, function(z)
switch(z,
ND.T="im_idx",
"ROI ID" = "reg_id",
totalSec = "time_sec",
Fura_Ratio = "fura_ratio",
ratio_340_380 = "fura_ratio",
"Ratio 340/380" = "fura_ratio",
ROI = "reg_id",
z))
convertTime <- function(dat) {
cn <- colnames(dat)
if("Time_ms" %in% cn & ! "time_sec" %in% cn) {
dat <- dat %>% separate(Time_ms, c("Minute", "Second"), sep = ":", convert = TRUE) %>%
mutate(time_sec = Minute*60 + Second) %>% select(all_of(setdiff(c(cn,"time_sec"),c("Time_ms","Minute","Second"))))
} else {
dat
}
}
processRawXLSX <- function(fpath, filter_min_area=100) {
stopifnot(any(grepl("\\.xlsx", fpath)))
expt_date <- substr(sapply(strsplit(fpath, "/"), "[[", 7),1,8)
cond <- NA
if(any(grepl("csv", fpath))) cond_temp <- sapply(strsplit(sapply(strsplit(fpath, "/"), "[[", 7), "\\.csv\\?"), "[[", 1)
if(any(grepl("xlsx", fpath))) cond_temp <- sapply(strsplit(sapply(strsplit(fpath, "/"), "[[", 7), "\\.xlsx\\?"), "[[", 1)
cond_pt1 <- sapply(strsplit(cond_temp, "_"), "[[", 2)
cond_pt2 <- sapply(strsplit(cond_temp, "_"), "[[", 3)
cond_pt3 <- ifelse(length(strsplit(cond_temp, "_")) >= 4, sapply(strsplit(cond_temp, "_"), "[[", 4), "")
cond_temp <- paste(cond_pt1, cond_pt2, cond_pt3, sep="_")
if(any(grepl("naive", cond_temp))) {
naive_temp <- c(cond_pt1,cond_pt2,cond_pt3)[grep("naive",c(cond_pt1,cond_pt2,cond_pt3))]
cond <- paste(expt_date, naive_temp, sep="_")
}
if(any(grepl("untreated", cond_temp)))
cond <- paste(expt_date, "naive", sep="_")
if(any(grepl("PLX",cond_temp)))
cond <- paste(expt_date, sapply(strsplit(cond_temp, "_"), "[[", 3), sep="_")
if(any(grepl("free",cond_temp)))
cond <- paste(expt_date, "3day8uMPLXCa2free", sep="_")
if(any(grepl("Ca2containing",cond_temp)))
cond <- paste(expt_date, "3day8uMPLX_w_Ca2", sep="_")
if(any(grepl("MEK",cond_temp)))
cond <- paste(expt_date, "30minMEKi", sep="_")
if(any(grepl("dropbox",fpath))) {
temp <- tempfile()
download.file(fpath, temp, mode="wb")
} else {
temp <- fpath
}
dat <- readxl::read_xlsx(temp)
keep_ocols <- c("Time [m:s]","ND.T","ROI ID","Intensity(340)","StDev(340)","Intensity(380)","StDev(380)","ROI Area [µm²]","Ratio 340/380")
dat <- dat[,keep_ocols]
times <- unique(dat$`Time [m:s]`)
diff_times <- difftime(times, times[1], units="secs", tz="UTC")
dat$time_sec <- DescTools::RoundTo(diff_times[match(dat$`Time [m:s]`,times)], multiple=5)
# remove objects < 100 µM^2
dat <- dat[dat$`ROI Area [µm²]` > filter_min_area,]
# fix colnames
colnames(dat) <- fixColnames(colnames(dat))
dat$expt_date <- expt_date
dat$cond <- cond
return(as.data.frame(dat[,c(std_colnames,"cond")]))
}
preprocessData <- function(fpath) {
expt_date <- substr(sapply(strsplit(fpath, "/"), "[[", 7),1,8)
cond_temp <- sapply(strsplit(sapply(strsplit(fpath, "/"), "[[", 7), "\\.csv\\?"), "[[", 1)
cond_pt1 <- sapply(strsplit(cond_temp, "_"), "[[", 2)
cond_pt2 <- sapply(strsplit(cond_temp, "_"), "[[", 3)
cond_pt3 <- sapply(strsplit(cond_temp, "_"), "[[", 4)
cond_temp <- paste(cond_pt1, cond_pt2, cond_pt3, sep="_")
if(any(grepl("naive", cond_temp)))
cond <- paste(expt_date, "naive", sep="_")
if(any(grepl("untreated", cond_temp)))
cond <- paste(expt_date, "naive", sep="_")
if(any(grepl("PLX",cond_temp)))
cond <- paste(expt_date, sapply(strsplit(cond_temp, "_"), "[[", 3), sep="_")
if(any(grepl("free",cond_temp)))
cond <- paste(expt_date, "8day8uMPLXCa2free", sep="_")
if(any(grepl("Ca2containing",cond_temp)))
cond <- paste(expt_date, "3day8uMPLX_w_Ca2", sep="_")
if(any(grepl("MEK",cond_temp)))
cond <- paste(expt_date, "30minMEKi", sep="_")
d <- read.csv(fpath)
d <- convertTime(d)
colnames(d) <- fixColnames(colnames(d))
d <- d[,intersect(std_colnames,colnames(d))]
d$expt_date <- expt_date
d$cond <- cond
return(d)
}
sampleROIs <- function(condition, ids=rois, n=100) sample(ids[grep(condition,ids)],n)
plotPCcatch <- function(roi, group_ids=clust$cluster, ...) {
groups <- unique(group_ids)
mycolors <- dichromat::colorschemes$Categorical.12[seq_along(groups)]
plot(pc_catch[['x']][,1], pc_catch[['x']][,2], type="n",
xlab="PC1", ylab="PC2", ...)
sapply(groups, function(group) points(pc_catch[['x']][which(group_ids==group & rois %in% roi),1],
pc_catch[['x']][which(group_ids==group & rois %in% roi),2],
col=mycolors[match(group,groups)],
pch=19, cex=0.5,))
return(invisible(NULL))
}
Datasets
20220425 = naive
20230426 = untreated
20211114 = tolerant? 3 days?
20220321_fura2_A375H2bRFP_4day8uMPLX
20220321_fura2_A375H2bRFP_5day8uMPLX
20230426_idlingCa2free_pulsing002_V02
fpaths <- c("https://www.dropbox.com/scl/fi/yprt6h4s2khr7sk0kaoav/20230426_untreated_pulsing_mod.csv?rlkey=bt575yvb612cgf1bi23fb9n99&dl=1",
# "https://www.dropbox.com/scl/fi/q5gxqbib7sjkbnzrjsl2q/20220425_Fura2_naive_pulsing_mod.csv?rlkey=9frhbyh7urxdm8s6h1k8n5vut&dl=1",
"https://www.dropbox.com/scl/fi/fk0fh1trr46aromfqd31k/20220321_fura2_A375H2bRFP_5day8uMPLX.csv?rlkey=jhhm821t8q63c3n6yb6udasxg&dl=1",
"https://www.dropbox.com/scl/fi/r0bitvu4ljz19jah9f9p8/20220321_fura2_A375H2bRFP_7day8uMPLX.csv?rlkey=i6wnyo48z5a0v4ovsrgysz2xm&dl=1",
"https://www.dropbox.com/scl/fi/9j6dvk9vnar3cn5xe24he/20220321_fura2_A375H2bRFP_10day8uMPLX.csv?rlkey=62l5qpih4oetwrte0k3bk3szr&dl=1",
"https://www.dropbox.com/scl/fi/6pq4d62ser0bcsuvwfcpv/20230426_idlingCa2free_pulsing002_V02_mod.csv?rlkey=k19wxifmfh3rn69zdgn0kb3tc&dl=1"
)
rawdatapaths <- c(
"https://www.dropbox.com/scl/fi/f0ap9tuwfldtrjk2pd3yn/20220425_Fura2_naive002.xlsx?rlkey=dx1zwj6ov8zwzqt944jwc05z9&dl=1",
"https://www.dropbox.com/scl/fi/ru1cunsuqgi691ua05dgs/20220425_Fura2_naive003.xlsx?rlkey=f1q7uh454sqjspmr3anabdy1k&dl=1"
)
fp <- "https://www.dropbox.com/s/elwck3cnpzvg4uf/20211114_A375_Ca2%2B_pulsing_data.csv?dl=1"
d <- read.csv(fp)
colnames(d) <- c("reg_id", "fura_ratio", "time_sec", "time_min")
d$expt_date <- "20211114"
d$im_idx <- match(d$time_sec, sort(unique(d$time_sec)))
colnames(d) <- fixColnames(colnames(d))
d <- d[,intersect(std_colnames,colnames(d))]
d$cond <- "20211114_14day8uMPLX"
d <- rbind(d, do.call(rbind, lapply(fpaths, preprocessData)))
NOTE
Experiments are each of different duration. To facilitate
interpretation, use number of time points of the shortest dataset for
each
expt_dates <- unique(d$expt_date)
conditions <- unique(d$cond)
n_im_idx <- min(sapply(conditions, function(co) length(unique((d[d$cond==co,"im_idx"])))))
temp <- lapply(unique(d$cond), function(co) {
a <- d[d$cond==co,]
a <- a[a$time_sec %in% tail(unique(a$time_sec), n_im_idx),]
a$time_sec <- DescTools::RoundTo(a$time_sec - min(a$time_sec), multiple=5)
# a$time_sec <- seq(0,((n_im_idx-1)*5),5)
a$im_idx <- match(a$time_sec, sort(unique(a$time_sec)))
rownames(a) <- NULL
return(a)
})
d <- do.call(rbind, temp)
rois <- unique(d$reg_id)
times <- unique(d$time_sec)
# fr = fura2_ratio
fr <- data.frame(matrix(d$fura_ratio, ncol=length(rois)))
colnames(fr) <- as.character(rois)
# linear model - intersect
l <- do.call(c, lapply(conditions, function(co) {
apply(fr[,grepl(co,colnames(fr))], 2, function(x) coef(lm(x ~ times))[2])
}))
# mean fura_ratio
fr_mean <- do.call(cbind,lapply(conditions, function(co) {
apply(fr[,grepl(co,colnames(fr))], 1, mean)
}))
colnames(fr_mean) <- conditions
# frn = fura2_ratio normalized (mean-centered)
frn <- do.call(cbind, lapply(conditions, function(co) {
apply(fr[,grepl(co,colnames(fr))], 2, function(x) x - fr_mean[,match(co,conditions)])
}))
d$fura_ratio_norm <- as.vector(frn)
d$int_mean_norm <- as.vector(frn)
Things to consider
- DTW
- Sum of time above threshold
- Stretches of times above threshold
- Times between spikes
- Number of spikes
- Determine the background for each trace
interesting ROIs: 11, 12, 18, 20, 25, 27
checkthese <- paste("20211114_14day8uMPLX", c(11, 12, 18, 20, 25, 27), sep="_")
plot(fura_ratio ~ time_sec, data=d[d$reg_id %in% checkthese,], type="n", ylim=c(0,1))
for(roi in checkthese) lines(fura_ratio ~ time_sec, data=d[d$reg_id==roi,], col=dichromat::colorschemes$Categorical.12[match(roi,checkthese)])

spike_height <- sd(d$fura_ratio_norm)*2.5
roi_list <- lapply(unique(d$reg_id), function(roi)
{
dat <- d[d$reg_id==roi,"fura_ratio_norm"]
thresh <- .15
out <- data.frame(reg_id=roi,
mean=mean(dat),
sd=sd(dat),
avg.spike.length=avgSpikeLength(dat, threshold=thresh),
num.runs=numRuns(dat, threshold=thresh),
num.spikes=numSpikes(dat,threshold=thresh),
slope=coef(lm(dat ~ times))[2],
intercept=coef(lm(dat ~ times))[1]
)
rownames(out) <- NULL
return(out)
})
sumstats <- do.call(rbind, roi_list)
sumstats$avg.spike.length[is.na(sumstats$avg.spike.length)] <- 0
catch <- lapply(unique(d$reg_id), function(roi)
{
dat <- catch22_all(d[d$reg_id==roi,"fura_ratio_norm"])
dat <- as.data.frame(dat)
rownames(dat) <- dat[,1]
dat$names <- NULL
dat <- t(dat)
return(dat)
})
Warning: As of 0.1.14 the feature 'CO_f1ecac' returns a double instead of int
pc_catch <- prcomp(scale(catch))
plot(pc_catch[['x']][,1], pc_catch[['x']][,2], pch=19, cex=0.5)

biplot(pc_catch, cex=0.5)

sumstats_scaled <- sumstats
sumstats_scaled[,-1] <- scale(sumstats_scaled[,-1])
lo_signal <- subset(sumstats, mean<0.05 & sd<0.01)
lo_ids <- lo_signal$reg_id
message(paste("There are",nrow(lo_signal),"cells with very low activity"))
There are 1942 cells with very low activity
Catch-22 variables
Location of low-signal traces in Catch-22 PCA space
plot(pc_catch[['x']][,1], pc_catch[['x']][,2], pch=19, cex=0.5, xlab="PC1", ylab="PC2")
points(pc_catch[['x']][lo_signal$reg_id,1], pc_catch[['x']][lo_signal$reg_id,2], pch=19, cex=0.5, col="yellow")

Boundaries of Catch-22 PCA space
IDs on the boundaries (manually identified from biplot above) 604,
522, 210, 870, 946, 539, 542, 744, 675
eps_val <- 2
clust <- dbscan::dbscan(pc_catch[['x']], eps=eps_val, minPts = 20)
mycols <- rev(dichromat::colorschemes$Categorical.12)[seq(length(unique(clust$cluster)))]
plot(pc_catch[['x']][,1], pc_catch[['x']][,2], pch=19, cex=0.5,
col=mycols[clust$cluster+1],
xlab="PC1", ylab="PC2")
mtext(paste("eps=",eps_val,"produced",length(unique(clust$cluster)),"clusters"), 3)
legend(-12,6,legend=paste("clust",seq(length(unique(clust$cluster)))), col=mycols, pch=19, cex=0.5)

message(cat("With eps=",eps_val,",",length(unique(clust$cluster)), "clusters were identified"))
With eps= 2 , 2 clusters were identified
diprate::nEach(clust$cluster)
0 1
1501 4111
No spikes looks like cluster==1
set.seed(10)
checkthese <- sample(rois[clust$cluster==1], 15)
plotTraceFura(checkthese, d)

Compare to lo_ids
plotTraceFura(lo_ids[1:15], d)

all_no_spike_ids <- union(rois[clust$cluster==1],lo_ids)
length(all_no_spike_ids)
[1] 4211
others (with spikes?) cluster==0
set.seed(6)
checkthese <- sample(rois[clust$cluster==0], 60)
plotTraceFura(checkthese[1:15], d)

Criteria
Philip described a few basic types of traces he sees:
- no spikes
- periodic spiking (oscillatory)
- sporadic individual spiking (rare and random)
- sporadic sustained spiking
- sustained activation (two-level trace)
Algorithms for classification
Fast fourier transform to detect regular oscillatory patterns
f <- mvfft(as.matrix(frn))
oscil <- sapply(rois, function(roi) any(Re(f[,as.character(roi)])[3:(nrow(f)-3)] > 10))
roi_oscil <- rois[oscil]
plot(seq(2,nrow(f)-1), tail(head(Re(f[,roi_oscil[2]]),-1),-1), type='l', ylim=c(-1,20))

Example oscillatory traces
plotTraceFura(roi_oscil[11:20], d)

plot(pc_catch[['x']][,1], pc_catch[['x']][,2], pch=19, cex=0.5,
col=c("blue","orange")[(sumstats$reg_id %in% roi_oscil)+1],
xlab="PC1", ylab="PC2")

Example traces with hi mean values
hi <- sumstats_scaled[sumstats_scaled$mean >=0.1 & sumstats_scaled$sd <= 0,"reg_id"]
plotTraceFura(hi[1:10], d)

Single spikes
single_spike <- sumstats[sumstats$num.spikes==1,"reg_id"]
mycols3 <- viridisLite::viridis(n=38)
plotTraceFura(single_spike[1:20], d)

hi_slope <- rois[which(l>4e-5)]
plotTraceFura(hi_slope[1:10], d)

plot(pc_catch[['x']][,1], pc_catch[['x']][,2], pch=19, cex=0.5,
col=c("blue","orange")[(sumstats$reg_id %in% hi_slope)+1],
xlab="PC1", ylab="PC2")

plot(pc_catch[['x']][,1], pc_catch[['x']][,2], pch=19, cex=0.5, xlab="PC1", ylab="PC2")
points(pc_catch[['x']][single_spike,1], pc_catch[['x']][single_spike,2], pch=19, cex=0.5, col="yellow")

Expt conditions
plot(pc_catch[['x']][,1], pc_catch[['x']][,2], pch=".", cex=0.5,
col=rev(dichromat::colorschemes$Categorical.12)[match(gsub("_[0-9]{1,3}$", "", sumstats$reg_id),conditions)],
xlab="PC1", ylab="PC2")
legend(-13,8, conditions, col=dichromat::colorschemes$Categorical.12[seq_along(conditions)], cex=0.5, pch=19)

set.seed(13)
my_samples <- invisible(lapply(conditions, function(cond) {
a <- sampleROIs(condition=cond)
plotPCcatch(a, clust$cluster, main=cond)
a
}))








set.seed(13)
my_samples <- invisible(lapply(conditions, function(cond) {
a <- sampleROIs(condition=cond)
plotPCcatch(a, as.integer(!rois %in% all_no_spike_ids), main=cond)
a
}))








lapply(seq_along(my_samples), function(i) plotTraceFura(my_samples[[i]][!my_samples[[i]] %in% all_no_spike_ids][1:20], d))
[[1]]
[[2]]
[[3]]
[[4]]
[[5]]
[[6]]
[[7]]
[[8]]








Example traces
Figure 1B
exemplar_ids <- c( "20211114_14day8uMPLX_105",
"20211114_14day8uMPLX_802",
"20211114_14day8uMPLX_297",
"20220321_7day8uMPLX_340",
"20220321_7day8uMPLX_1121",
"20211114_14day8uMPLX_604",
"20211114_14day8uMPLX_20"
)
spike_examples <- d[d$reg_id %in% exemplar_ids, ]
spike_examples$reg_id <- factor(spike_examples$reg_id, levels=exemplar_ids)
plotTraceFura(exemplar_ids, spike_examples, yl=c(0.5,1.25))

# ggsave("example_fura2_traces.png", device="png", width=5, height=3.5, units="in")
Plot % in each cluster for each condition
Perform bootstrapping to get sample statistics. Assessing proportions
of cells in clusters 0 and 1
# filtered objects in naive data (should filter all datasets) with area > 100 µM^2
set.seed(13)
prop_spiking <- do.call(c, lapply(conditions, function(co) {
samp <- lapply(1:100, function(i) sample(rois[grepl(co,rois)],200, replace=TRUE))
sapply(samp, function(x) length(which(!x %in% all_no_spike_ids))/200)
}))
prop_spiking_df <- data.frame(condition=rep(conditions, each=100),
prop_spiking=prop_spiking)
prop_spiking_cond_df <- prop_spiking_df
prop_spiking_cond_df$date <- sapply(strsplit(prop_spiking_cond_df$condition, "_"), "[[", 1)
prop_spiking_cond_df$condition <- sapply(strsplit(prop_spiking_cond_df$condition, "_"), "[[", 2)
prop_spiking_cond_df$condition <- gsub("day8uMPLX","",prop_spiking_cond_df$condition)
prop_spiking_cond_df[grep("naive",prop_spiking_cond_df$condition),"condition"] <- 0
prop_spiking_cond_df$condition <- factor(prop_spiking_cond_df$condition, levels=c("0", "5","7","10","14"))
prop_spiking_cond_df <- prop_spiking_cond_df[prop_spiking_cond_df$date != "20230426",]
Time dependence of spiking behavior
Figure 1C
p <- ggplot(data=prop_spiking_cond_df, aes(x=condition, y=prop_spiking, fill=1)) +
geom_violin(width=.75) + geom_boxplot(width=0.1, color="grey", alpha=0.5) +
# coord_flip() +
theme(legend.position="none",
axis.text.x = element_text(size=16),
axis.text.y = element_text(size=16),
axis.title.x = element_text(size=18, face="bold"),
axis.title.y = element_text(size=18, face="bold")) +
ylim(c(0,0.75)) +
ylab("Proportion spiking") +
xlab("Days in BRAFi")
p

# ggsave("BRAFi_time_prop_spiking.png", device="png", width=4, height=4, units="in")
m <- lm(prop_spiking ~ condition, data=prop_spiking_cond_df)
a <- aov(m)
tukey <- TukeyHSD(a)
tukey$condition
diff lwr upr p adj
5-0 0.075075 0.065782529 0.08436747 4.747650e-10
7-0 0.204875 0.195582529 0.21416747 4.747650e-10
10-0 0.209325 0.200032529 0.21861747 4.747650e-10
14-0 0.531025 0.521732529 0.54031747 4.747650e-10
7-5 0.129800 0.119069979 0.14053002 4.747650e-10
10-5 0.134250 0.123519979 0.14498002 4.747650e-10
14-5 0.455950 0.445219979 0.46668002 4.747650e-10
10-7 0.004450 -0.006280021 0.01518002 7.880375e-01
14-7 0.326150 0.315419979 0.33688002 4.747650e-10
14-10 0.321700 0.310969979 0.33243002 4.747650e-10
Number of cells per condition
sapply(conditions, function(co) length(unique(rois[grep(co,rois)])))
20211114_14day8uMPLX 20230426_naive 20220321_5day8uMPLX 20220321_7day8uMPLX 20220321_10day8uMPLX
882 224 1202 1141 1152
20230426_8day8uMPLXCa2free 20220425_naive002 20220425_naive003
266 383 362
LS0tCnRpdGxlOiAiQ2FjbGl1bSBwdWxzaW5nIGFuYWx5c2lzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKZGF0ZTogMjAyMy0wNC0yMwphdXRob3I6IERhcnJlbiBUeXNvbiAmIFBoaWxpcCBTdGF1ZmZlcgotLS0KIyMgRmlyc3QtcGFzcyBhdHRlbXB0IGF0IGNoYXJhY3Rlcml6aW5nIGNhbGNpdW0gc3Bpa2VzCgpgYGB7ciBMb2FkIHBhY2thZ2VzIGFuZCBmdW5jdGlvbnN9CiMgaW5zdGFsbC5wYWNrYWdlcygiUmNhdGNoMjIiKQojIGluc3RhbGwucGFja2FnZXMoImRpY2hyb21hdCIpCiMgaW5zdGFsbC5wYWNrYWdlcygiRGVzY1Rvb2xzIikKCmxpYnJhcnkoUmNhdGNoMjIpCmxpYnJhcnkoc21vb3RoKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKc291cmNlKCJ0cmFjZUFuYWx5c2lzRnhucy5yIikKCm51bVJ1bnMgPC0gZnVuY3Rpb24oeCwgdGhyZXNob2xkPTAuMixsZW49MikgCnsKICAgIHhbeD49dGhyZXNob2xkXQogICAgb3V0IDwtIHJsZSh4KSRsZW5ndGhzCiAgICBsZW5ndGgob3V0W291dD49bGVuXSkKfQoKbnVtU3Bpa2VzIDwtIGZ1bmN0aW9uKHgsIHRocmVzaG9sZD0wLjE3KQp7CiAgICBsZW5ndGgod2hpY2goeD49dGhyZXNob2xkKSkKfQphdmdTcGlrZUxlbmd0aCA8LSBmdW5jdGlvbih4LCB0aHJlc2hvbGQ9MC4xNykgCnsKICAgIHB0c19hYm92ZV90aCA8LSB4W3g+PXRocmVzaG9sZF0KICAgIG91dCA8LSB0cnlDYXRjaCh7cmxlKHB0c19hYm92ZV90aCkkbGVuZ3Roc30sZXJyb3I9ZnVuY3Rpb24oY29uZCl7TkF9KQogICAgdHJ5Q2F0Y2goe21lYW4ob3V0KX0sZXJyb3I9ZnVuY3Rpb24oY29uZCl7bWVzc2FnZShjb25kKTtOQX0pCn0KCmF2Z1RpbWVCZXR3ZWVuU3Bpa2VzIDwtIGZ1bmN0aW9uKHgsIHRocmVzaG9sZD0wLjE3KQp7CiAgICBwdHNfYWJvdmVfdGggPC0geFt4Pj10aHJlc2hvbGRdCiAgICBvdXQgPC0gdHJ5Q2F0Y2goe2ludmVyc2UucmxlKHB0c19hYm92ZV90aCkkbGVuZ3Roc30sZXJyb3I9ZnVuY3Rpb24oY29uZCl7TkF9KQogICAgdHJ5Q2F0Y2goe21lYW4ob3V0KX0sZXJyb3I9ZnVuY3Rpb24oY29uZCl7TkF9KQp9CgoKc3RkX2NvbG5hbWVzIDwtIGMoInJlZ19pZCIsImltX2lkeCIsInRpbWVfc2VjIiwiZnVyYV9yYXRpbyIsImV4cHRfZGF0ZSIpCgpmaXhDb2xuYW1lcyA8LSBmdW5jdGlvbih4KSBzYXBwbHkoeCwgZnVuY3Rpb24oeikgCiAgICBzd2l0Y2goeiwKICAgICAgICAgICBORC5UPSJpbV9pZHgiLAogICAgICAgICAgICJST0kgSUQiID0gInJlZ19pZCIsCiAgICAgICAgICAgdG90YWxTZWMgPSAidGltZV9zZWMiLAogICAgICAgICAgIEZ1cmFfUmF0aW8gPSAiZnVyYV9yYXRpbyIsCiAgICAgICAgICAgcmF0aW9fMzQwXzM4MCA9ICJmdXJhX3JhdGlvIiwKICAgICAgICAgICAiUmF0aW8gMzQwLzM4MCIgPSAiZnVyYV9yYXRpbyIsCiAgICAgICAgICAgUk9JID0gInJlZ19pZCIsCiAgICAgICAgICAgeikpCgpjb252ZXJ0VGltZSA8LSBmdW5jdGlvbihkYXQpIHsKICAgIGNuIDwtIGNvbG5hbWVzKGRhdCkKICAgIGlmKCJUaW1lX21zIiAlaW4lIGNuICYgISAidGltZV9zZWMiICVpbiUgY24pIHsKICAgICAgICBkYXQgPC0gZGF0ICU+JSBzZXBhcmF0ZShUaW1lX21zLCBjKCJNaW51dGUiLCAiU2Vjb25kIiksIHNlcCA9ICI6IiwgY29udmVydCA9IFRSVUUpICU+JSAKICAgICAgICAgICAgbXV0YXRlKHRpbWVfc2VjID0gTWludXRlKjYwICsgU2Vjb25kKSAgJT4lIHNlbGVjdChhbGxfb2Yoc2V0ZGlmZihjKGNuLCJ0aW1lX3NlYyIpLGMoIlRpbWVfbXMiLCJNaW51dGUiLCJTZWNvbmQiKSkpKQogICAgfSBlbHNlIHsKICAgICAgICBkYXQKICAgIH0KfQoKcHJvY2Vzc1Jhd1hMU1ggPC0gZnVuY3Rpb24oZnBhdGgsIGZpbHRlcl9taW5fYXJlYT0xMDApIHsKICAgIHN0b3BpZm5vdChhbnkoZ3JlcGwoIlxcLnhsc3giLCBmcGF0aCkpKQogICAgCiAgICBleHB0X2RhdGUgPC0gc3Vic3RyKHNhcHBseShzdHJzcGxpdChmcGF0aCwgIi8iKSwgIltbIiwgNyksMSw4KQogICAgY29uZCA8LSBOQQoKICAgIGlmKGFueShncmVwbCgiY3N2IiwgZnBhdGgpKSkgY29uZF90ZW1wIDwtIHNhcHBseShzdHJzcGxpdChzYXBwbHkoc3Ryc3BsaXQoZnBhdGgsICIvIiksICJbWyIsIDcpLCAiXFwuY3N2XFw/IiksICJbWyIsIDEpCiAgICBpZihhbnkoZ3JlcGwoInhsc3giLCBmcGF0aCkpKSBjb25kX3RlbXAgPC0gc2FwcGx5KHN0cnNwbGl0KHNhcHBseShzdHJzcGxpdChmcGF0aCwgIi8iKSwgIltbIiwgNyksICJcXC54bHN4XFw/IiksICJbWyIsIDEpCgogICAgY29uZF9wdDEgPC0gc2FwcGx5KHN0cnNwbGl0KGNvbmRfdGVtcCwgIl8iKSwgIltbIiwgMikKICAgIGNvbmRfcHQyIDwtIHNhcHBseShzdHJzcGxpdChjb25kX3RlbXAsICJfIiksICJbWyIsIDMpCiAgICBjb25kX3B0MyA8LSBpZmVsc2UobGVuZ3RoKHN0cnNwbGl0KGNvbmRfdGVtcCwgIl8iKSkgPj0gNCwgc2FwcGx5KHN0cnNwbGl0KGNvbmRfdGVtcCwgIl8iKSwgIltbIiwgNCksICIiKQogICAgY29uZF90ZW1wIDwtIHBhc3RlKGNvbmRfcHQxLCBjb25kX3B0MiwgY29uZF9wdDMsIHNlcD0iXyIpCgogICAgaWYoYW55KGdyZXBsKCJuYWl2ZSIsIGNvbmRfdGVtcCkpKSB7CiAgICAgICAgbmFpdmVfdGVtcCA8LSBjKGNvbmRfcHQxLGNvbmRfcHQyLGNvbmRfcHQzKVtncmVwKCJuYWl2ZSIsYyhjb25kX3B0MSxjb25kX3B0Mixjb25kX3B0MykpXQogICAgICAgIGNvbmQgPC0gcGFzdGUoZXhwdF9kYXRlLCBuYWl2ZV90ZW1wLCBzZXA9Il8iKQogICAgfQogICAgaWYoYW55KGdyZXBsKCJ1bnRyZWF0ZWQiLCBjb25kX3RlbXApKSkgCiAgICAgICAgY29uZCA8LSBwYXN0ZShleHB0X2RhdGUsICJuYWl2ZSIsIHNlcD0iXyIpCiAgICBpZihhbnkoZ3JlcGwoIlBMWCIsY29uZF90ZW1wKSkpIAogICAgICAgIGNvbmQgPC0gcGFzdGUoZXhwdF9kYXRlLCBzYXBwbHkoc3Ryc3BsaXQoY29uZF90ZW1wLCAiXyIpLCAiW1siLCAzKSwgc2VwPSJfIikKICAgIGlmKGFueShncmVwbCgiZnJlZSIsY29uZF90ZW1wKSkpIAogICAgICAgIGNvbmQgPC0gcGFzdGUoZXhwdF9kYXRlLCAiM2RheTh1TVBMWENhMmZyZWUiLCBzZXA9Il8iKQogICAgaWYoYW55KGdyZXBsKCJDYTJjb250YWluaW5nIixjb25kX3RlbXApKSkgCiAgICAgICAgY29uZCA8LSBwYXN0ZShleHB0X2RhdGUsICIzZGF5OHVNUExYX3dfQ2EyIiwgc2VwPSJfIikKICAgIGlmKGFueShncmVwbCgiTUVLIixjb25kX3RlbXApKSkgCiAgICAgICAgY29uZCA8LSBwYXN0ZShleHB0X2RhdGUsICIzMG1pbk1FS2kiLCBzZXA9Il8iKQoKICAgIGlmKGFueShncmVwbCgiZHJvcGJveCIsZnBhdGgpKSkgewogICAgICAgIHRlbXAgPC0gdGVtcGZpbGUoKQogICAgICAgIGRvd25sb2FkLmZpbGUoZnBhdGgsIHRlbXAsIG1vZGU9IndiIikKICAgIH0gZWxzZSB7CiAgICAgICAgdGVtcCA8LSBmcGF0aAogICAgfQogICAgZGF0IDwtIHJlYWR4bDo6cmVhZF94bHN4KHRlbXApCiAgICBrZWVwX29jb2xzIDwtIGMoIlRpbWUgW206c10iLCJORC5UIiwiUk9JIElEIiwiSW50ZW5zaXR5KDM0MCkiLCJTdERldigzNDApIiwiSW50ZW5zaXR5KDM4MCkiLCJTdERldigzODApIiwiUk9JIEFyZWEgW8K1bcKyXSIsIlJhdGlvIDM0MC8zODAiKQogICAgZGF0IDwtIGRhdFssa2VlcF9vY29sc10KICAgIHRpbWVzIDwtIHVuaXF1ZShkYXQkYFRpbWUgW206c11gKQogICAgZGlmZl90aW1lcyA8LSBkaWZmdGltZSh0aW1lcywgdGltZXNbMV0sIHVuaXRzPSJzZWNzIiwgdHo9IlVUQyIpCiAgICBkYXQkdGltZV9zZWMgPC0gRGVzY1Rvb2xzOjpSb3VuZFRvKGRpZmZfdGltZXNbbWF0Y2goZGF0JGBUaW1lIFttOnNdYCx0aW1lcyldLCBtdWx0aXBsZT01KQogICAgIyByZW1vdmUgb2JqZWN0cyA8IDEwMCDCtU1eMgogICAgZGF0IDwtIGRhdFtkYXQkYFJPSSBBcmVhIFvCtW3Csl1gID4gZmlsdGVyX21pbl9hcmVhLF0KICAgIAogICAgIyBmaXggY29sbmFtZXMKICAgIGNvbG5hbWVzKGRhdCkgPC0gZml4Q29sbmFtZXMoY29sbmFtZXMoZGF0KSkKICAgIGRhdCRleHB0X2RhdGUgPC0gZXhwdF9kYXRlCiAgICBkYXQkY29uZCA8LSBjb25kCiAgICByZXR1cm4oYXMuZGF0YS5mcmFtZShkYXRbLGMoc3RkX2NvbG5hbWVzLCJjb25kIildKSkKfQoKcHJlcHJvY2Vzc0RhdGEgPC0gZnVuY3Rpb24oZnBhdGgpIHsKICAgIGV4cHRfZGF0ZSA8LSBzdWJzdHIoc2FwcGx5KHN0cnNwbGl0KGZwYXRoLCAiLyIpLCAiW1siLCA3KSwxLDgpCgogICAgY29uZF90ZW1wIDwtIHNhcHBseShzdHJzcGxpdChzYXBwbHkoc3Ryc3BsaXQoZnBhdGgsICIvIiksICJbWyIsIDcpLCAiXFwuY3N2XFw/IiksICJbWyIsIDEpCiAgICBjb25kX3B0MSA8LSBzYXBwbHkoc3Ryc3BsaXQoY29uZF90ZW1wLCAiXyIpLCAiW1siLCAyKQogICAgY29uZF9wdDIgPC0gc2FwcGx5KHN0cnNwbGl0KGNvbmRfdGVtcCwgIl8iKSwgIltbIiwgMykKICAgIGNvbmRfcHQzIDwtIHNhcHBseShzdHJzcGxpdChjb25kX3RlbXAsICJfIiksICJbWyIsIDQpCiAgICBjb25kX3RlbXAgPC0gcGFzdGUoY29uZF9wdDEsIGNvbmRfcHQyLCBjb25kX3B0Mywgc2VwPSJfIikKCiAgICBpZihhbnkoZ3JlcGwoIm5haXZlIiwgY29uZF90ZW1wKSkpIAogICAgICAgIGNvbmQgPC0gcGFzdGUoZXhwdF9kYXRlLCAibmFpdmUiLCBzZXA9Il8iKQogICAgaWYoYW55KGdyZXBsKCJ1bnRyZWF0ZWQiLCBjb25kX3RlbXApKSkgCiAgICAgICAgY29uZCA8LSBwYXN0ZShleHB0X2RhdGUsICJuYWl2ZSIsIHNlcD0iXyIpCiAgICBpZihhbnkoZ3JlcGwoIlBMWCIsY29uZF90ZW1wKSkpIAogICAgICAgIGNvbmQgPC0gcGFzdGUoZXhwdF9kYXRlLCBzYXBwbHkoc3Ryc3BsaXQoY29uZF90ZW1wLCAiXyIpLCAiW1siLCAzKSwgc2VwPSJfIikKICAgIGlmKGFueShncmVwbCgiZnJlZSIsY29uZF90ZW1wKSkpIAogICAgICAgIGNvbmQgPC0gcGFzdGUoZXhwdF9kYXRlLCAiOGRheTh1TVBMWENhMmZyZWUiLCBzZXA9Il8iKQogICAgaWYoYW55KGdyZXBsKCJDYTJjb250YWluaW5nIixjb25kX3RlbXApKSkgCiAgICAgICAgY29uZCA8LSBwYXN0ZShleHB0X2RhdGUsICIzZGF5OHVNUExYX3dfQ2EyIiwgc2VwPSJfIikKICAgIGlmKGFueShncmVwbCgiTUVLIixjb25kX3RlbXApKSkgCiAgICAgICAgY29uZCA8LSBwYXN0ZShleHB0X2RhdGUsICIzMG1pbk1FS2kiLCBzZXA9Il8iKQogICAgCgogICAgZCA8LSByZWFkLmNzdihmcGF0aCkKICAgIGQgPC0gY29udmVydFRpbWUoZCkKICAgIGNvbG5hbWVzKGQpIDwtIGZpeENvbG5hbWVzKGNvbG5hbWVzKGQpKQogICAgZCA8LSBkWyxpbnRlcnNlY3Qoc3RkX2NvbG5hbWVzLGNvbG5hbWVzKGQpKV0KICAgIGQkZXhwdF9kYXRlIDwtIGV4cHRfZGF0ZQogICAgZCRjb25kIDwtIGNvbmQKICAgIHJldHVybihkKQp9CgpzYW1wbGVST0lzIDwtIGZ1bmN0aW9uKGNvbmRpdGlvbiwgaWRzPXJvaXMsIG49MTAwKSBzYW1wbGUoaWRzW2dyZXAoY29uZGl0aW9uLGlkcyldLG4pCgpwbG90UENjYXRjaCA8LSBmdW5jdGlvbihyb2ksIGdyb3VwX2lkcz1jbHVzdCRjbHVzdGVyLCAuLi4pIHsKICAgIGdyb3VwcyA8LSB1bmlxdWUoZ3JvdXBfaWRzKQogICAgbXljb2xvcnMgPC0gZGljaHJvbWF0Ojpjb2xvcnNjaGVtZXMkQ2F0ZWdvcmljYWwuMTJbc2VxX2Fsb25nKGdyb3VwcyldCiAgICBwbG90KHBjX2NhdGNoW1sneCddXVssMV0sIHBjX2NhdGNoW1sneCddXVssMl0sIHR5cGU9Im4iLAogICAgICAgICB4bGFiPSJQQzEiLCB5bGFiPSJQQzIiLCAuLi4pCiAgICBzYXBwbHkoZ3JvdXBzLCBmdW5jdGlvbihncm91cCkgcG9pbnRzKHBjX2NhdGNoW1sneCddXVt3aGljaChncm91cF9pZHM9PWdyb3VwICYgcm9pcyAlaW4lIHJvaSksMV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwY19jYXRjaFtbJ3gnXV1bd2hpY2goZ3JvdXBfaWRzPT1ncm91cCAmIHJvaXMgJWluJSByb2kpLDJdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sPW15Y29sb3JzW21hdGNoKGdyb3VwLGdyb3VwcyldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwY2g9MTksIGNleD0wLjUsKSkKICAgIHJldHVybihpbnZpc2libGUoTlVMTCkpCn0KCmBgYAojIyMgRGF0YXNldHMKMjAyMjA0MjUgPSBuYWl2ZSAgCjIwMjMwNDI2ID0gdW50cmVhdGVkICAKMjAyMTExMTQgPSB0b2xlcmFudD8gMyBkYXlzPyAgCjIwMjIwMzIxX2Z1cmEyX0EzNzVIMmJSRlBfNGRheTh1TVBMWAoyMDIyMDMyMV9mdXJhMl9BMzc1SDJiUkZQXzVkYXk4dU1QTFgKMjAyMzA0MjZfaWRsaW5nQ2EyZnJlZV9wdWxzaW5nMDAyX1YwMgoKYGBge3IgTG9hZCBhbmQgY29tcGlsZSBkYXRhfQpmcGF0aHMgPC0gYygiaHR0cHM6Ly93d3cuZHJvcGJveC5jb20vc2NsL2ZpL3lwcnQ2aDRzMmtocjdzazBrYW9hdi8yMDIzMDQyNl91bnRyZWF0ZWRfcHVsc2luZ19tb2QuY3N2P3Jsa2V5PWJ0NTc1eXZiNjEyY2dmMWJpMjNmYjluOTkmZGw9MSIsCiAgICAgICAgICAgICMgImh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3NjbC9maS9xNWd4cWJpYjdzamtibnpyanNsMnEvMjAyMjA0MjVfRnVyYTJfbmFpdmVfcHVsc2luZ19tb2QuY3N2P3Jsa2V5PTlmcmhieWg3dXJ4ZG04czZoMWs4bjV2dXQmZGw9MSIsCiAgICAgICAgICAgICJodHRwczovL3d3dy5kcm9wYm94LmNvbS9zY2wvZmkvZmswZmgxdHJyNDZhcm9tZnFkMzFrLzIwMjIwMzIxX2Z1cmEyX0EzNzVIMmJSRlBfNWRheTh1TVBMWC5jc3Y/cmxrZXk9amhobTgyMXQ4cTYzYzNuNnliNnVkYXN4ZyZkbD0xIiwKICAgICAgICAgICAgImh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3NjbC9maS9yMGJpdHZ1NGxqejE5amFoOWY5cDgvMjAyMjAzMjFfZnVyYTJfQTM3NUgyYlJGUF83ZGF5OHVNUExYLmNzdj9ybGtleT1pNndueW80OHo1YTB2NG92c3JneXN6MnhtJmRsPTEiLAogICAgICAgICAgICAiaHR0cHM6Ly93d3cuZHJvcGJveC5jb20vc2NsL2ZpLzlqNmR2azl2bmFyM2NuNXhlMjRoZS8yMDIyMDMyMV9mdXJhMl9BMzc1SDJiUkZQXzEwZGF5OHVNUExYLmNzdj9ybGtleT02Mmw1cXBpaDRvZXR3cnRlMGszYmszc3pyJmRsPTEiLAogICAgICAgICAgICAgImh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3NjbC9maS82cHE0ZDYyc2VyMGJjc3V2d2ZjcHYvMjAyMzA0MjZfaWRsaW5nQ2EyZnJlZV9wdWxzaW5nMDAyX1YwMl9tb2QuY3N2P3Jsa2V5PWsxOXd4aWZtZmgzcm42OXpkZ24wa2IzdGMmZGw9MSIKICAgICAgICAgICAgKQoKcmF3ZGF0YXBhdGhzIDwtIGMoCiAgICAgICAgICAgICAgICAgICJodHRwczovL3d3dy5kcm9wYm94LmNvbS9zY2wvZmkvZjBhcDl0dXdmbGR0cmprMnBkM3luLzIwMjIwNDI1X0Z1cmEyX25haXZlMDAyLnhsc3g/cmxrZXk9ZHgxendqNm92OHp3enF0OTQ0andjMDV6OSZkbD0xIiwKICAgICAgICAgICAgICAgICAgImh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3NjbC9maS9ydTFjdW5zdXFnaTY5MXVhMDVkZ3MvMjAyMjA0MjVfRnVyYTJfbmFpdmUwMDMueGxzeD9ybGtleT1mMXE3dWg0NTRzcWpzcG1yM2FuYWJkeTFrJmRsPTEiCiAgICAgICAgICAgICAgICAgICkKCmZwIDwtICJodHRwczovL3d3dy5kcm9wYm94LmNvbS9zL2Vsd2NrM2NucHp2ZzR1Zi8yMDIxMTExNF9BMzc1X0NhMiUyQl9wdWxzaW5nX2RhdGEuY3N2P2RsPTEiCgpkIDwtIHJlYWQuY3N2KGZwKQpjb2xuYW1lcyhkKSA8LSBjKCJyZWdfaWQiLCAiZnVyYV9yYXRpbyIsICJ0aW1lX3NlYyIsICJ0aW1lX21pbiIpCmQkZXhwdF9kYXRlIDwtICIyMDIxMTExNCIKZCRpbV9pZHggPC0gbWF0Y2goZCR0aW1lX3NlYywgc29ydCh1bmlxdWUoZCR0aW1lX3NlYykpKQpjb2xuYW1lcyhkKSA8LSBmaXhDb2xuYW1lcyhjb2xuYW1lcyhkKSkKZCA8LSBkWyxpbnRlcnNlY3Qoc3RkX2NvbG5hbWVzLGNvbG5hbWVzKGQpKV0KZCRjb25kIDwtICIyMDIxMTExNF8xNGRheTh1TVBMWCIKCmQgPC0gcmJpbmQoZCwgZG8uY2FsbChyYmluZCwgbGFwcGx5KGZwYXRocywgcHJlcHJvY2Vzc0RhdGEpKSkKZCA8LSByYmluZChkLCBkby5jYWxsKHJiaW5kLCBsYXBwbHkocmF3ZGF0YXBhdGhzLCBwcm9jZXNzUmF3WExTWCkpKQoKIyBjYWxjdWxhdGUvYWRqdXN0IGNvbHVtbnMKZCRyZWdfaWQgPC0gcGFzdGUoZCRjb25kLCBkJHJlZ19pZCwgc2VwPSJfIikKZCRpbnRlbnNpdHlfbWVhbiA8LSBkJGZ1cmFfcmF0aW8KCiMgZW5zdXJlIGFsbCB0aW1lcyBhcmUgYSBtdWx0aXBsZSBvZiA1CmQkdGltZV9zZWMgPC0gRGVzY1Rvb2xzOjpSb3VuZFRvKGQkdGltZV9zZWMsIG11bHRpcGxlPTUpCmBgYAoKCgojIyMgTk9URQpFeHBlcmltZW50cyBhcmUgZWFjaCBvZiBkaWZmZXJlbnQgZHVyYXRpb24uIFRvIGZhY2lsaXRhdGUgaW50ZXJwcmV0YXRpb24sIHVzZSBudW1iZXIgb2YgdGltZSBwb2ludHMgb2YgdGhlIHNob3J0ZXN0IGRhdGFzZXQgZm9yIGVhY2gKCmBgYHtyfQpleHB0X2RhdGVzIDwtIHVuaXF1ZShkJGV4cHRfZGF0ZSkKY29uZGl0aW9ucyA8LSB1bmlxdWUoZCRjb25kKQoKbl9pbV9pZHggPC0gbWluKHNhcHBseShjb25kaXRpb25zLCBmdW5jdGlvbihjbykgbGVuZ3RoKHVuaXF1ZSgoZFtkJGNvbmQ9PWNvLCJpbV9pZHgiXSkpKSkpCgp0ZW1wIDwtIGxhcHBseSh1bmlxdWUoZCRjb25kKSwgZnVuY3Rpb24oY28pIHsKICAgIGEgPC0gZFtkJGNvbmQ9PWNvLF0KICAgIGEgPC0gYVthJHRpbWVfc2VjICVpbiUgdGFpbCh1bmlxdWUoYSR0aW1lX3NlYyksIG5faW1faWR4KSxdCiAgICBhJHRpbWVfc2VjIDwtIERlc2NUb29sczo6Um91bmRUbyhhJHRpbWVfc2VjIC0gbWluKGEkdGltZV9zZWMpLCBtdWx0aXBsZT01KQogICAgIyBhJHRpbWVfc2VjIDwtIHNlcSgwLCgobl9pbV9pZHgtMSkqNSksNSkKICAgIGEkaW1faWR4IDwtIG1hdGNoKGEkdGltZV9zZWMsIHNvcnQodW5pcXVlKGEkdGltZV9zZWMpKSkKICAgIHJvd25hbWVzKGEpIDwtIE5VTEwKICAgIHJldHVybihhKQp9KQoKZCA8LSBkby5jYWxsKHJiaW5kLCB0ZW1wKQpgYGAKCgpgYGB7cn0Kcm9pcyA8LSB1bmlxdWUoZCRyZWdfaWQpCnRpbWVzIDwtIHVuaXF1ZShkJHRpbWVfc2VjKQoKIyBmciA9IGZ1cmEyX3JhdGlvCmZyIDwtIGRhdGEuZnJhbWUobWF0cml4KGQkZnVyYV9yYXRpbywgbmNvbD1sZW5ndGgocm9pcykpKQpjb2xuYW1lcyhmcikgPC0gYXMuY2hhcmFjdGVyKHJvaXMpCgojIGxpbmVhciBtb2RlbCAtIGludGVyc2VjdApsIDwtIGRvLmNhbGwoYywgbGFwcGx5KGNvbmRpdGlvbnMsIGZ1bmN0aW9uKGNvKSB7CiAgICBhcHBseShmclssZ3JlcGwoY28sY29sbmFtZXMoZnIpKV0sIDIsIGZ1bmN0aW9uKHgpIGNvZWYobG0oeCB+IHRpbWVzKSlbMl0pCn0pKQoKCiMgbWVhbiBmdXJhX3JhdGlvCmZyX21lYW4gPC0gZG8uY2FsbChjYmluZCxsYXBwbHkoY29uZGl0aW9ucywgZnVuY3Rpb24oY28pIHsKICAgIGFwcGx5KGZyWyxncmVwbChjbyxjb2xuYW1lcyhmcikpXSwgMSwgbWVhbikKfSkpCmNvbG5hbWVzKGZyX21lYW4pIDwtIGNvbmRpdGlvbnMKCiMgZnJuID0gZnVyYTJfcmF0aW8gbm9ybWFsaXplZCAobWVhbi1jZW50ZXJlZCkKZnJuIDwtIGRvLmNhbGwoY2JpbmQsIGxhcHBseShjb25kaXRpb25zLCBmdW5jdGlvbihjbykgewogICAgYXBwbHkoZnJbLGdyZXBsKGNvLGNvbG5hbWVzKGZyKSldLCAyLCBmdW5jdGlvbih4KSB4IC0gZnJfbWVhblssbWF0Y2goY28sY29uZGl0aW9ucyldKQp9KSkKCmQkZnVyYV9yYXRpb19ub3JtIDwtIGFzLnZlY3Rvcihmcm4pCmQkaW50X21lYW5fbm9ybSA8LSBhcy52ZWN0b3IoZnJuKQpgYGAKCgojIyMgVGhpbmdzIHRvIGNvbnNpZGVyCiogRFRXICAKKiBTdW0gb2YgdGltZSBhYm92ZSB0aHJlc2hvbGQgIAoqIFN0cmV0Y2hlcyBvZiB0aW1lcyBhYm92ZSB0aHJlc2hvbGQKKiBUaW1lcyBiZXR3ZWVuIHNwaWtlcwoqIE51bWJlciBvZiBzcGlrZXMKKiBEZXRlcm1pbmUgdGhlIGJhY2tncm91bmQgZm9yIGVhY2ggdHJhY2UKCmludGVyZXN0aW5nIFJPSXM6IDExLCAxMiwgMTgsIDIwLCAyNSwgMjcKYGBge3J9CmNoZWNrdGhlc2UgPC0gcGFzdGUoIjIwMjExMTE0XzE0ZGF5OHVNUExYIiwgYygxMSwgMTIsIDE4LCAyMCwgMjUsIDI3KSwgc2VwPSJfIikKcGxvdChmdXJhX3JhdGlvIH4gdGltZV9zZWMsIGRhdGE9ZFtkJHJlZ19pZCAlaW4lIGNoZWNrdGhlc2UsXSwgdHlwZT0ibiIsIHlsaW09YygwLDEpKQpmb3Iocm9pIGluIGNoZWNrdGhlc2UpIGxpbmVzKGZ1cmFfcmF0aW8gfiB0aW1lX3NlYywgZGF0YT1kW2QkcmVnX2lkPT1yb2ksXSwgY29sPWRpY2hyb21hdDo6Y29sb3JzY2hlbWVzJENhdGVnb3JpY2FsLjEyW21hdGNoKHJvaSxjaGVja3RoZXNlKV0pCmBgYAoKYGBge3J9CnNwaWtlX2hlaWdodCA8LSBzZChkJGZ1cmFfcmF0aW9fbm9ybSkqMi41CnJvaV9saXN0IDwtIGxhcHBseSh1bmlxdWUoZCRyZWdfaWQpLCBmdW5jdGlvbihyb2kpIAogICAgewogICAgICAgIGRhdCA8LSBkW2QkcmVnX2lkPT1yb2ksImZ1cmFfcmF0aW9fbm9ybSJdCiAgICAgICAgdGhyZXNoIDwtIC4xNQogICAgICAgIG91dCA8LSBkYXRhLmZyYW1lKHJlZ19pZD1yb2ksCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWVhbj1tZWFuKGRhdCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2Q9c2QoZGF0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICBhdmcuc3Bpa2UubGVuZ3RoPWF2Z1NwaWtlTGVuZ3RoKGRhdCwgdGhyZXNob2xkPXRocmVzaCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtLnJ1bnM9bnVtUnVucyhkYXQsIHRocmVzaG9sZD10aHJlc2gpLAogICAgICAgICAgICAgICAgICAgICAgICAgIG51bS5zcGlrZXM9bnVtU3Bpa2VzKGRhdCx0aHJlc2hvbGQ9dGhyZXNoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzbG9wZT1jb2VmKGxtKGRhdCB+IHRpbWVzKSlbMl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgaW50ZXJjZXB0PWNvZWYobG0oZGF0IH4gdGltZXMpKVsxXQogICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICByb3duYW1lcyhvdXQpIDwtIE5VTEwKICAgICAgICByZXR1cm4ob3V0KQogICAgfSkKCnN1bXN0YXRzIDwtIGRvLmNhbGwocmJpbmQsIHJvaV9saXN0KQpzdW1zdGF0cyRhdmcuc3Bpa2UubGVuZ3RoW2lzLm5hKHN1bXN0YXRzJGF2Zy5zcGlrZS5sZW5ndGgpXSA8LSAwCgpjYXRjaCA8LSBsYXBwbHkodW5pcXVlKGQkcmVnX2lkKSwgZnVuY3Rpb24ocm9pKSAKICAgIHsKICAgICAgICBkYXQgPC0gY2F0Y2gyMl9hbGwoZFtkJHJlZ19pZD09cm9pLCJmdXJhX3JhdGlvX25vcm0iXSkKICAgICAgICBkYXQgPC0gYXMuZGF0YS5mcmFtZShkYXQpCiAgICAgICAgcm93bmFtZXMoZGF0KSA8LSBkYXRbLDFdCiAgICAgICAgZGF0JG5hbWVzIDwtIE5VTEwKICAgICAgICBkYXQgPC0gdChkYXQpCgogICAgICAgIHJldHVybihkYXQpCiAgICB9KQoKY2F0Y2ggPC0gZG8uY2FsbChyYmluZCwgY2F0Y2gpCnJvd25hbWVzKGNhdGNoKSA8LSB1bmlxdWUoZCRyZWdfaWQpCmBgYAoKYGBge3IgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9NH0KcGNfY2F0Y2ggPC0gcHJjb21wKHNjYWxlKGNhdGNoKSkKcGxvdChwY19jYXRjaFtbJ3gnXV1bLDFdLCBwY19jYXRjaFtbJ3gnXV1bLDJdLCBwY2g9MTksIGNleD0wLjUpCmBgYApgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD02fQpiaXBsb3QocGNfY2F0Y2gsIGNleD0wLjUpCmBgYAoKCgpgYGB7cn0Kc3Vtc3RhdHNfc2NhbGVkIDwtIHN1bXN0YXRzCnN1bXN0YXRzX3NjYWxlZFssLTFdIDwtIHNjYWxlKHN1bXN0YXRzX3NjYWxlZFssLTFdKQoKbG9fc2lnbmFsIDwtIHN1YnNldChzdW1zdGF0cywgbWVhbjwwLjA1ICYgc2Q8MC4wMSkKbG9faWRzIDwtIGxvX3NpZ25hbCRyZWdfaWQKbWVzc2FnZShwYXN0ZSgiVGhlcmUgYXJlIixucm93KGxvX3NpZ25hbCksImNlbGxzIHdpdGggdmVyeSBsb3cgYWN0aXZpdHkiKSkKYGBgCiMjIyBDYXRjaC0yMiB2YXJpYWJsZXMKTG9jYXRpb24gb2YgbG93LXNpZ25hbCB0cmFjZXMgaW4gQ2F0Y2gtMjIgUENBIHNwYWNlCmBgYHtyIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTR9CnBsb3QocGNfY2F0Y2hbWyd4J11dWywxXSwgcGNfY2F0Y2hbWyd4J11dWywyXSwgcGNoPTE5LCBjZXg9MC41LCB4bGFiPSJQQzEiLCB5bGFiPSJQQzIiKQpwb2ludHMocGNfY2F0Y2hbWyd4J11dW2xvX3NpZ25hbCRyZWdfaWQsMV0sIHBjX2NhdGNoW1sneCddXVtsb19zaWduYWwkcmVnX2lkLDJdLCBwY2g9MTksIGNleD0wLjUsIGNvbD0ieWVsbG93IikKCmBgYAojIyBCb3VuZGFyaWVzIG9mIENhdGNoLTIyIFBDQSBzcGFjZQpJRHMgb24gdGhlIGJvdW5kYXJpZXMgKG1hbnVhbGx5IGlkZW50aWZpZWQgZnJvbSBiaXBsb3QgYWJvdmUpCjYwNCwgNTIyLCAyMTAsIDg3MCwgOTQ2LCA1MzksIDU0MiwgNzQ0LCA2NzUKCgpgYGB7ciBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD00fQplcHNfdmFsIDwtIDIKY2x1c3QgPC0gZGJzY2FuOjpkYnNjYW4ocGNfY2F0Y2hbWyd4J11dLCBlcHM9ZXBzX3ZhbCwgbWluUHRzID0gMjApCgpteWNvbHMgPC0gcmV2KGRpY2hyb21hdDo6Y29sb3JzY2hlbWVzJENhdGVnb3JpY2FsLjEyKVtzZXEobGVuZ3RoKHVuaXF1ZShjbHVzdCRjbHVzdGVyKSkpXQoKcGxvdChwY19jYXRjaFtbJ3gnXV1bLDFdLCBwY19jYXRjaFtbJ3gnXV1bLDJdLCBwY2g9MTksIGNleD0wLjUsIAogICAgIGNvbD1teWNvbHNbY2x1c3QkY2x1c3RlcisxXSwKICAgICB4bGFiPSJQQzEiLCB5bGFiPSJQQzIiKQptdGV4dChwYXN0ZSgiZXBzPSIsZXBzX3ZhbCwicHJvZHVjZWQiLGxlbmd0aCh1bmlxdWUoY2x1c3QkY2x1c3RlcikpLCJjbHVzdGVycyIpLCAzKQpsZWdlbmQoLTEyLDYsbGVnZW5kPXBhc3RlKCJjbHVzdCIsc2VxKGxlbmd0aCh1bmlxdWUoY2x1c3QkY2x1c3RlcikpKSksIGNvbD1teWNvbHMsIHBjaD0xOSwgY2V4PTAuNSkKYGBgCgpgYGB7cn0KbWVzc2FnZShjYXQoIldpdGggZXBzPSIsZXBzX3ZhbCwiLCIsbGVuZ3RoKHVuaXF1ZShjbHVzdCRjbHVzdGVyKSksICJjbHVzdGVycyB3ZXJlIGlkZW50aWZpZWQiKSkKZGlwcmF0ZTo6bkVhY2goY2x1c3QkY2x1c3RlcikKYGBgCgojIyMgTm8gc3Bpa2VzIGxvb2tzIGxpa2UgY2x1c3Rlcj09MQpgYGB7cn0Kc2V0LnNlZWQoMTApCmNoZWNrdGhlc2UgPC0gc2FtcGxlKHJvaXNbY2x1c3QkY2x1c3Rlcj09MV0sIDE1KQpwbG90VHJhY2VGdXJhKGNoZWNrdGhlc2UsIGQpCmBgYAojIyMgQ29tcGFyZSB0byBsb19pZHMKYGBge3J9CnBsb3RUcmFjZUZ1cmEobG9faWRzWzE6MTVdLCBkKQpgYGAKYGBge3J9CmFsbF9ub19zcGlrZV9pZHMgPC0gdW5pb24ocm9pc1tjbHVzdCRjbHVzdGVyPT0xXSxsb19pZHMpCmxlbmd0aChhbGxfbm9fc3Bpa2VfaWRzKQpgYGAKCiMjIyBvdGhlcnMgKHdpdGggc3Bpa2VzPykgY2x1c3Rlcj09MApgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD02fQpzZXQuc2VlZCg2KQpjaGVja3RoZXNlIDwtIHNhbXBsZShyb2lzW2NsdXN0JGNsdXN0ZXI9PTBdLCA2MCkKcGxvdFRyYWNlRnVyYShjaGVja3RoZXNlWzE6MTVdLCBkKQpgYGAKCiMjIyBDcml0ZXJpYQpQaGlsaXAgZGVzY3JpYmVkIGEgZmV3IGJhc2ljIHR5cGVzIG9mIHRyYWNlcyBoZSBzZWVzOgoKMSkgbm8gc3Bpa2VzICAKMSkgcGVyaW9kaWMgc3Bpa2luZyAob3NjaWxsYXRvcnkpICAKMSkgc3BvcmFkaWMgaW5kaXZpZHVhbCBzcGlraW5nIChyYXJlIGFuZCByYW5kb20pICAKMSkgc3BvcmFkaWMgc3VzdGFpbmVkIHNwaWtpbmcgIAoxKSBzdXN0YWluZWQgYWN0aXZhdGlvbiAodHdvLWxldmVsIHRyYWNlKSAgCgoKIyMjIEFsZ29yaXRobXMgZm9yIGNsYXNzaWZpY2F0aW9uCgpGYXN0IGZvdXJpZXIgdHJhbnNmb3JtICB0byBkZXRlY3QgcmVndWxhciBvc2NpbGxhdG9yeSBwYXR0ZXJucwpgYGB7cn0KZiA8LSBtdmZmdChhcy5tYXRyaXgoZnJuKSkKCm9zY2lsIDwtIHNhcHBseShyb2lzLCBmdW5jdGlvbihyb2kpIGFueShSZShmWyxhcy5jaGFyYWN0ZXIocm9pKV0pWzM6KG5yb3coZiktMyldID4gMTApKQpyb2lfb3NjaWwgPC0gcm9pc1tvc2NpbF0KYGBgCgpgYGB7cn0KcGxvdChzZXEoMixucm93KGYpLTEpLCB0YWlsKGhlYWQoUmUoZlsscm9pX29zY2lsWzJdXSksLTEpLC0xKSwgdHlwZT0nbCcsIHlsaW09YygtMSwyMCkpCmBgYAoKIyMjIEV4YW1wbGUgb3NjaWxsYXRvcnkgdHJhY2VzCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTZ9CnBsb3RUcmFjZUZ1cmEocm9pX29zY2lsWzExOjIwXSwgZCkKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD00fQpwbG90KHBjX2NhdGNoW1sneCddXVssMV0sIHBjX2NhdGNoW1sneCddXVssMl0sIHBjaD0xOSwgY2V4PTAuNSwgCiAgICAgY29sPWMoImJsdWUiLCJvcmFuZ2UiKVsoc3Vtc3RhdHMkcmVnX2lkICVpbiUgcm9pX29zY2lsKSsxXSwKICAgICB4bGFiPSJQQzEiLCB5bGFiPSJQQzIiKQoKYGBgCgojIyMgRXhhbXBsZSB0cmFjZXMgd2l0aCBoaSBtZWFuIHZhbHVlcwpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD02fQpoaSA8LSBzdW1zdGF0c19zY2FsZWRbc3Vtc3RhdHNfc2NhbGVkJG1lYW4gPj0wLjEgJiBzdW1zdGF0c19zY2FsZWQkc2QgPD0gMCwicmVnX2lkIl0KCnBsb3RUcmFjZUZ1cmEoaGlbMToxMF0sIGQpCmBgYAoKCiMgU2luZ2xlIHNwaWtlcwpgYGB7cn0Kc2luZ2xlX3NwaWtlIDwtIHN1bXN0YXRzW3N1bXN0YXRzJG51bS5zcGlrZXM9PTEsInJlZ19pZCJdCm15Y29sczMgPC0gdmlyaWRpc0xpdGU6OnZpcmlkaXMobj0zOCkKYGBgCgoKYGBge3IgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9Nn0KcGxvdFRyYWNlRnVyYShzaW5nbGVfc3Bpa2VbMToyMF0sIGQpCmBgYAoKYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9Nn0KaGlfc2xvcGUgPC0gcm9pc1t3aGljaChsPjRlLTUpXQpwbG90VHJhY2VGdXJhKGhpX3Nsb3BlWzE6MTBdLCBkKQoKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD00fQpwbG90KHBjX2NhdGNoW1sneCddXVssMV0sIHBjX2NhdGNoW1sneCddXVssMl0sIHBjaD0xOSwgY2V4PTAuNSwgCiAgICAgY29sPWMoImJsdWUiLCJvcmFuZ2UiKVsoc3Vtc3RhdHMkcmVnX2lkICVpbiUgaGlfc2xvcGUpKzFdLAogICAgIHhsYWI9IlBDMSIsIHlsYWI9IlBDMiIpCmBgYAoKYGBge3IgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9NH0KcGxvdChwY19jYXRjaFtbJ3gnXV1bLDFdLCBwY19jYXRjaFtbJ3gnXV1bLDJdLCBwY2g9MTksIGNleD0wLjUsIHhsYWI9IlBDMSIsIHlsYWI9IlBDMiIpCnBvaW50cyhwY19jYXRjaFtbJ3gnXV1bc2luZ2xlX3NwaWtlLDFdLCBwY19jYXRjaFtbJ3gnXV1bc2luZ2xlX3NwaWtlLDJdLCBwY2g9MTksIGNleD0wLjUsIGNvbD0ieWVsbG93IikKCmBgYAojIyMgRXhwdCBjb25kaXRpb25zCmBgYHtyIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTR9CnBsb3QocGNfY2F0Y2hbWyd4J11dWywxXSwgcGNfY2F0Y2hbWyd4J11dWywyXSwgcGNoPSIuIiwgY2V4PTAuNSwgCiAgICAgY29sPXJldihkaWNocm9tYXQ6OmNvbG9yc2NoZW1lcyRDYXRlZ29yaWNhbC4xMilbbWF0Y2goZ3N1YigiX1swLTldezEsM30kIiwgIiIsIHN1bXN0YXRzJHJlZ19pZCksY29uZGl0aW9ucyldLAogICAgIHhsYWI9IlBDMSIsIHlsYWI9IlBDMiIpCmxlZ2VuZCgtMTMsOCwgY29uZGl0aW9ucywgY29sPWRpY2hyb21hdDo6Y29sb3JzY2hlbWVzJENhdGVnb3JpY2FsLjEyW3NlcV9hbG9uZyhjb25kaXRpb25zKV0sIGNleD0wLjUsIHBjaD0xOSkKYGBgCgoKYGBge3IgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9NH0Kc2V0LnNlZWQoMTMpCm15X3NhbXBsZXMgPC0gaW52aXNpYmxlKGxhcHBseShjb25kaXRpb25zLCBmdW5jdGlvbihjb25kKSB7CiAgICBhIDwtIHNhbXBsZVJPSXMoY29uZGl0aW9uPWNvbmQpCiAgICBwbG90UENjYXRjaChhLCBjbHVzdCRjbHVzdGVyLCBtYWluPWNvbmQpCiAgICBhCn0pKQpgYGAKCgpgYGB7cn0Kc2V0LnNlZWQoMTMpCm15X3NhbXBsZXMgPC0gaW52aXNpYmxlKGxhcHBseShjb25kaXRpb25zLCBmdW5jdGlvbihjb25kKSB7CiAgICBhIDwtIHNhbXBsZVJPSXMoY29uZGl0aW9uPWNvbmQpCiAgICBwbG90UENjYXRjaChhLCBhcy5pbnRlZ2VyKCFyb2lzICVpbiUgYWxsX25vX3NwaWtlX2lkcyksIG1haW49Y29uZCkKICAgIGEKfSkpCmBgYAoKCgpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD02fQpsYXBwbHkoc2VxX2Fsb25nKG15X3NhbXBsZXMpLCBmdW5jdGlvbihpKSBwbG90VHJhY2VGdXJhKG15X3NhbXBsZXNbW2ldXVshbXlfc2FtcGxlc1tbaV1dICVpbiUgYWxsX25vX3NwaWtlX2lkc11bMToyMF0sIGQpKQpgYGAKCiMjIyBFeGFtcGxlIHRyYWNlcwpGaWd1cmUgMUIKYGBge3IgZmlnLmhlaWdodD0zLjUsIGZpZy53aWR0aD01fQpleGVtcGxhcl9pZHMgPC0gYyggICIyMDIxMTExNF8xNGRheTh1TVBMWF8xMDUiLAogICAgICAgICAgICAgICAgICAgICIyMDIxMTExNF8xNGRheTh1TVBMWF84MDIiLAogICAgICAgICAgICAgICAgICAgICIyMDIxMTExNF8xNGRheTh1TVBMWF8yOTciLAogICAgICAgICAgICAgICAgICAgICIyMDIyMDMyMV83ZGF5OHVNUExYXzM0MCIsCiAgICAgICAgICAgICAgICAgICAgIjIwMjIwMzIxXzdkYXk4dU1QTFhfMTEyMSIsCiAgICAgICAgICAgICAgICAgICAgIjIwMjExMTE0XzE0ZGF5OHVNUExYXzYwNCIsCiAgICAgICAgICAgICAgICAgICAgIjIwMjExMTE0XzE0ZGF5OHVNUExYXzIwIgopCgpzcGlrZV9leGFtcGxlcyA8LSBkW2QkcmVnX2lkICVpbiUgZXhlbXBsYXJfaWRzLCBdCnNwaWtlX2V4YW1wbGVzJHJlZ19pZCA8LSBmYWN0b3Ioc3Bpa2VfZXhhbXBsZXMkcmVnX2lkLCBsZXZlbHM9ZXhlbXBsYXJfaWRzKQoKcGxvdFRyYWNlRnVyYShleGVtcGxhcl9pZHMsIHNwaWtlX2V4YW1wbGVzLCB5bD1jKDAuNSwxLjI1KSkKCiMgZ2dzYXZlKCJleGFtcGxlX2Z1cmEyX3RyYWNlcy5wbmciLCBkZXZpY2U9InBuZyIsIHdpZHRoPTUsIGhlaWdodD0zLjUsIHVuaXRzPSJpbiIpCmBgYAoKCiMjIyBQbG90ICUgaW4gZWFjaCBjbHVzdGVyIGZvciBlYWNoIGNvbmRpdGlvbgpQZXJmb3JtIGJvb3RzdHJhcHBpbmcgdG8gZ2V0IHNhbXBsZSBzdGF0aXN0aWNzLiBBc3Nlc3NpbmcgcHJvcG9ydGlvbnMgb2YgY2VsbHMgaW4gY2x1c3RlcnMgMCBhbmQgMSAKCmBgYHtyfQojIGZpbHRlcmVkIG9iamVjdHMgaW4gbmFpdmUgZGF0YSAoc2hvdWxkIGZpbHRlciBhbGwgZGF0YXNldHMpIHdpdGggYXJlYSA+IDEwMCDCtU1eMiAKCnNldC5zZWVkKDEzKQoKcHJvcF9zcGlraW5nIDwtIGRvLmNhbGwoYywgbGFwcGx5KGNvbmRpdGlvbnMsIGZ1bmN0aW9uKGNvKSB7CiAgICBzYW1wIDwtIGxhcHBseSgxOjEwMCwgZnVuY3Rpb24oaSkgc2FtcGxlKHJvaXNbZ3JlcGwoY28scm9pcyldLDIwMCwgcmVwbGFjZT1UUlVFKSkKICAgIHNhcHBseShzYW1wLCBmdW5jdGlvbih4KSBsZW5ndGgod2hpY2goIXggJWluJSBhbGxfbm9fc3Bpa2VfaWRzKSkvMjAwKQp9KSkKCnByb3Bfc3Bpa2luZ19kZiA8LSBkYXRhLmZyYW1lKGNvbmRpdGlvbj1yZXAoY29uZGl0aW9ucywgZWFjaD0xMDApLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvcF9zcGlraW5nPXByb3Bfc3Bpa2luZykKCnByb3Bfc3Bpa2luZ19jb25kX2RmIDwtIHByb3Bfc3Bpa2luZ19kZgpwcm9wX3NwaWtpbmdfY29uZF9kZiRkYXRlIDwtICBzYXBwbHkoc3Ryc3BsaXQocHJvcF9zcGlraW5nX2NvbmRfZGYkY29uZGl0aW9uLCAiXyIpLCAiW1siLCAxKQpwcm9wX3NwaWtpbmdfY29uZF9kZiRjb25kaXRpb24gPC0gIHNhcHBseShzdHJzcGxpdChwcm9wX3NwaWtpbmdfY29uZF9kZiRjb25kaXRpb24sICJfIiksICJbWyIsIDIpCnByb3Bfc3Bpa2luZ19jb25kX2RmJGNvbmRpdGlvbiA8LSAgZ3N1YigiZGF5OHVNUExYIiwiIixwcm9wX3NwaWtpbmdfY29uZF9kZiRjb25kaXRpb24pCnByb3Bfc3Bpa2luZ19jb25kX2RmW2dyZXAoIm5haXZlIixwcm9wX3NwaWtpbmdfY29uZF9kZiRjb25kaXRpb24pLCJjb25kaXRpb24iXSA8LSAgMApwcm9wX3NwaWtpbmdfY29uZF9kZiRjb25kaXRpb24gPC0gIGZhY3Rvcihwcm9wX3NwaWtpbmdfY29uZF9kZiRjb25kaXRpb24sIGxldmVscz1jKCIwIiwgIjUiLCI3IiwiMTAiLCIxNCIpKQpwcm9wX3NwaWtpbmdfY29uZF9kZiA8LSBwcm9wX3NwaWtpbmdfY29uZF9kZltwcm9wX3NwaWtpbmdfY29uZF9kZiRkYXRlICE9ICIyMDIzMDQyNiIsXQpgYGAKCiMjIyBUaW1lIGRlcGVuZGVuY2Ugb2Ygc3Bpa2luZyBiZWhhdmlvcgpGaWd1cmUgMUMKYGBge3IgVmlvbGluIHBsb3QsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTR9CnAgPC0gZ2dwbG90KGRhdGE9cHJvcF9zcGlraW5nX2NvbmRfZGYsIGFlcyh4PWNvbmRpdGlvbiwgeT1wcm9wX3NwaWtpbmcsIGZpbGw9MSkpICsgCiAgICBnZW9tX3Zpb2xpbih3aWR0aD0uNzUpICsgZ2VvbV9ib3hwbG90KHdpZHRoPTAuMSwgY29sb3I9ImdyZXkiLCBhbHBoYT0wLjUpICsgCiAgICAjIGNvb3JkX2ZsaXAoKSArIAogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiwgCiAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplPTE2KSwKICAgICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemU9MTgsIGZhY2U9ImJvbGQiKSwKICAgICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplPTE4LCBmYWNlPSJib2xkIikpICsgCiAgICB5bGltKGMoMCwwLjc1KSkgKwogICAgeWxhYigiUHJvcG9ydGlvbiBzcGlraW5nIikgKyAKICAgIHhsYWIoIkRheXMgaW4gQlJBRmkiKQpwCgojIGdnc2F2ZSgiQlJBRmlfdGltZV9wcm9wX3NwaWtpbmcucG5nIiwgZGV2aWNlPSJwbmciLCB3aWR0aD00LCBoZWlnaHQ9NCwgdW5pdHM9ImluIikKYGBgCgoKYGBge3J9Cm0gPC0gbG0ocHJvcF9zcGlraW5nIH4gY29uZGl0aW9uLCBkYXRhPXByb3Bfc3Bpa2luZ19jb25kX2RmKQphIDwtIGFvdihtKQp0dWtleSA8LSBUdWtleUhTRChhKQp0dWtleSRjb25kaXRpb24KYGBgCgoKIyMjIE51bWJlciBvZiBjZWxscyBwZXIgY29uZGl0aW9uCmBgYHtyfQpzYXBwbHkoY29uZGl0aW9ucywgZnVuY3Rpb24oY28pIGxlbmd0aCh1bmlxdWUocm9pc1tncmVwKGNvLHJvaXMpXSkpKQpgYGAKCgoKCg==